Technical Note TN2059
Using collection classes safely with multithreaded applications

目次

このテクニカルノートでは、マルチスレッドの Cocoa アプリケーションにおいて可変コレクションクラス(配列、辞書、集合)を使うときに発生する可能性のあるいくつかの問題について、またこれらの問題を解決するためのいくつかの方法について、実装を踏まえながら説明します。

マルチスレッドの Core Foundation アプリケーションでも同様の問題が生じる可能性があります。なぜなら、Cocoa クラスと Core Foundation クラスは自由に相互利用が可能だ。このテクニカルノートでは、Core Foundation のコレクションは扱いませんが、ここで説明する原則のほとんどは、Core Foundation にも当てはまります。Core Foundation の詳細については、参考文献Overview of Core Foundation を参照してください。

このテクニカルノートではスレッド処理に関する一般的な説明はしません。入門知識を得るには、参考文献のリストを参照してください。

[2002 年 9 月 10 日]






マルチスレッドアプリケーションにおける共有データに関する問題

2 つのスレッドがデータを共有するときは、必ずデータアクセスを同期化して、同時に同じデータを扱う場合に起こりうるバグを回避しなければなりません。

ほとんどの場合、スレッドは、ロックを使用して同期化を行い、ほかのスレッドが同時に同じコードに入るのを防ぎます。 たとえば、次のようになります。

NSLock *statisticsLock; // このオブジェクトが存在するものと仮定する;
    ...
[statisticsLock lock];    // 2 つのスレッドが同時に
statistics += 1;    // 「statistics」を更新しないようにする
[statisticsLock unlock];    // ロックを解放する

statisticsLock を使用することで、「+=」演算を実行できるスレッドは 一度に 1 つだけであることが保証されます。このロックがなければ、2 つのスレッドが同時に同じ場所を更新しようとし、一方のスレッドの statistics に対する作業結果が失われます。

先頭に戻る



マルチスレッドアプリケーションにおけるコレクションクラスに関する問題

コードの中でが、可変辞書または可変配列を操作するとき場合、その中身を操作している間だけオブジェクトをロックしてもすべてのバグを防げない可能性があります。

その理由を理解するために、次のコードについて考えてみましょう(本文書で示すコードはすべて、辞書を使用していますが、配列や集合の場合でも問題は同じです)。

    NSLock *dictionaryLock; // このオブジェクトが存在するものと仮定する
    NSDictionary *aDictionary; // このオブジェクトが存在するものと仮定する

    NSString *theName;
        ...
    [dictionaryLock lock];
    theName = [aDictionary objectForKey: @"name"];
    [dictionaryLock unlock];

    NSLog (@"the name is '%@'", theName);

このコードは、可変辞書へのアクセスを保護します。しかし、辞書の同時使用を避けるために、プログラムのほかのすべての場所で同じロックを使ったとしても、このコードではうまくいかない場合があります。次にその例を示します。

可変コレクションは、オブジェクトを削除するときに、オブジェクトに release (解放)メッセージを送ります。 このことを念頭に置いて、次のシーケンスを考えてみましょう。

  1. aDictionary@"name" キーに対して @"Pat" という名前が含まれているものとします。その保持カウントは 1 です。
  2. A スレッドが上記のコードを実行します。 つまり、ロックを確保し、 objectForKey を送り、その後ロックを解放します。これで theName 変数の値は、@"Pat" となり、保持カウントは 1 です。
  3. 次に B スレッドがロックを確保し、辞書を次のように更新します。

    [aDictionary setObject: @"Sandy"  forKey: @"name"];
    

    辞書は、キーの前の値である @"Pat" に release メッセージを送ります。最初の保持カウントは 1 だったので、この解放によりカウントは 0 になり、文字列 @"Pat" は、割り当てを解除されます。

  4. 今度は A スレッドが、NSLog(...) 呼び出しの中で変数 theName を使おうとしますが、この変数は、割り当てを解除された @"Pat" を参照しているので、クラッシュまたはほかの予期できない結果が生じます。

このシーケンスは、マルチスレッドアプリケーションでコレクションクラスを扱う難しさの典型的な例を示しますが、念頭に置いておくべき問題はこれだけではありません。次の例を簡単に見てみましょう。

コードの中でオブジェクトを辞書に追加するとします。辞書は値を保持するので、コードではオブジェクトを追加した後にオブジェクトを解放します。 コードは次のようになります。

NSString *theName; // これが存在するものと仮定する
    ...
[aDictionaryLock lock];
[aDictionary setObject: theName  forKey: KEY];
[theNamerelease]; // 辞書がこれを保持するので、保持する必要はない
[aDictionaryLock unlock];

NSLog (@"the name is '%@'", theName);

このコードの持つ危険性は、前のコードが持つ危険性と似ています。つまり、コードがロックを解除した後で、別のスレッドが辞書を変更し、値を解放する可能性があります。 ここでも、theName が参照する値は、ロックを解除してから NSLog(...) の中で使うまでの短い間に、割り当てを解除される可能性があります。

辞書だけでなく、配列および集合に格納されているオブジェクトでも問題は同じです。B スレッドが、A スレッドがポインタを持っているオブジェクトを配列や集合から削除すれば、A スレッドがオブジェクトを使おうとしているスコープ内で、オブジェクトの有効性を維持できないかもしれません。

まとめると次のようになります。

  • 複数のスレッドがコレクションオブジェクトを共有するときには、そのコレクションへのアクセスを同期化する必要があります。
  • コレクションオブジェクトは、オブジェクトを削除する(または置き換える)ときに、オブジェクトに release メッセージを送ります。
  • 1 つのスレッドがコレクションにオブジェクトを追加、またはコレクションからオブジェクトを取得した後に、次のスレッドがそのオブジェクトを無効にする可能性があります。
  • つまり、複数のスレッドがコレクションオブジェクトを共有する場合、コレクションオブジェクトに含まれる値は、1 つのスレッドが値の参照を保持していたとしても、すべてほかのスレッドと共有されます。

先頭に戻る



例 1:不具合のあるアプリケーション

次のソースコードは、スレッド間でコレクションを共有する際の危険性を示します。可変辞書への同時アクセスを防ぐためにロックを使っていますがそれでも、常にクラッシュします。

#import <Foundation/Foundation.h>

static NSMutableDictionary    *aDictionary = nil;
static NSLock                *aDictionaryLock = nil;

@implementation NSMutableDictionary (Churning)

#define KEY        @"key"

- (void) churnContents;
{
    unsigned long    i;

    for (i = 0; ; i++)
    {
        NSAutoreleasePool    *pool;

        pool = [[NSAutoreleasePool alloc] init];

        [aDictionaryLock lock];
        [self setObject: [NSString stringWithFormat: @"%d", i]  forKey: KEY];
        [aDictionaryLock unlock];

        [pool release];
    }
}

@end

#define COUNT    10000

static void doGets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
    {
        NSObject    *anObject;

        //    辞書の値を取得し、その後値にメッセージを送信する
        [aDictionaryLock lock];
        anObject = [aDictionary objectForKey: KEY];
        [aDictionaryLock unlock];

        [anObject description];
    }
}

static void doSets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
    {
        NSObject    *anObject;

        anObject = [[NSObject alloc] init];

        [aDictionaryLock lock];
        [aDictionary setObject: anObject  forKey: KEY];
        [anObject release];
        [aDictionaryLock unlock];

        [anObject description];
    }
}

int main ()
{
    SEL        threadSelector;

    [[NSAutoreleasePool alloc] init];

    threadSelector = @selector(churnContents);

    aDictionary = [NSMutableDictionary dictionary];
    aDictionaryLock = [[NSLock alloc] init];

    //    辞書の反復処理を開始。
    //    あるキーに対応する 1 つの値を繰り返し新しい値で置き換える
    [NSThread detachNewThreadSelector: threadSelector
                toTarget: aDictionary
                withObject: nil];

#if 1 // これはクラッシュするので、これを無効にすれば、doSets() もクラッシュすることを確められる 
    doGets();
#endif
    doSets();

    return 0;
}

リスト 1 main1.m (「ダウンロード」セクションから入手可能)

マルチスレッドアプリケーションのバグは断続的にしか発生しないかもしれません。問題を確認するために、このコードを繰り返し実行する必要がないように、このアプリケーションは可変辞書に churnContents メッセージを送るスレッドを作成します。この方法では辞書のある値を、オブジェクトで繰り返し置き換えます。

このコードには、doGets() 関数と doSets() 関数が含まれており、両方ともアプリケーションをクラッシュさせる可能性があります。最初の関数への呼び出しをコメントアウトすることで、もう一方がクラッシュするかどうかを確かめられます。doGets() 関数は、繰り返し辞書から値を取得し、その値に description (記述)メッセージを送ります。その結果クラッシュが起きます。

doSets() 関数は、値を辞書に追加し、辞書がそれを保持するという前提でその値を解放します。そのオブジェクトに description を送ることにより、同様にクラッシュの危険性があります(description を使うことに特に意味はありません。どの方法でも結果は同じです)。

先頭に戻る



共有オブジェクトに関する安全策

コレクションから取得したオブジェクト、またはコレクションに保存したオブジェクトをコードの中で安全に扱うにはどうすればよいのでしょうか。その 1 つの方法として、オブジェクトに retain (保持) メッセージを送り、その後、つまりオブジェクトの操作が終わったときに、その効果を相殺する release メッセージをオブジェクトに送ります。この 2 つの処理の結果、最終的にはオブジェクトに変化はありません。しかし、保持と解放の間、コードはオブジェクトに対する権利を維持するので、ほかのスレッドによる割り当ての解除を防げます。

この方法の問題点は、release メッセージを送ることを覚えておかなければならないことです。メッセージを送らなければ、オブジェクトの割り当ては解除されず、「リーク」が起こります。操作終了時まで待って release を送る代わりに、すぐに autorelease (自動解放)メッセージを送ることもできます。この方法を取れば、autorelease のプールが割り当てを解除されたときに、確実にオブジェクトが解放されます。

retain と release は、人にお金を貸してくれるように頼み、返却すると自分で約束するようなものと考えてください。retain と autorelease は、人にお金を貸してくれるように頼み、相手が本人に返却させると約束するようなものと考えてください。 後者の方法は、帳尻が合うことが保証されているのでより堅実です (ただし、autorelease は、release よりも時間もスペースも余計に必要です)。

上記のコードをより安全にするためには、例 1 の前に示したコードに、次のコード中の太字の 1 行を追加する必要があります。

NSLock *dictionaryLock; // このオブジェクトが存在するものと仮定する
NSDictionary *aDictionary; // このオブジェクトが存在するものと仮定する

NSString *theName;
    ...
[dictionaryLock lock];
theName = [aDictionary objectForKey: @"name"];
[[theName retain] autorelease]; // オブジェクトをしばらく保持する
[dictionaryLock unlock];

NSLog (@"the name is '%@'", theName);

retainautorelease を合わせて使って何か変化があるようには思えないかもしれませんが、retain はすぐに有効になる一方で、autorelease は、retain の効果を後で解除します。これら 2 つのメッセージの効果は最終的には相殺されますが、retain は、コードがオブジェクトを使っている間オブジェクトの割り当て解除を防ぐ機能を果たします。

先頭に戻る



例 2:不具合を修正したアプリケーション

#import <Foundation/Foundation.h>

static NSMutableDictionary    *aDictionary = nil;
static NSLock                *aDictionaryLock = nil;

@implementation NSMutableDictionary (Churning)

#define KEY        @"key"

- (void) churnContents;
{
    unsigned long    i;

    for (i = 0; ; i++)
    {
        NSAutoreleasePool    *pool;

        pool = [[NSAutoreleasePool alloc] init];

        [aDictionaryLock lock];
        [self setObject: [NSString stringWithFormat: @"%d", i]  forKey: KEY];
        [aDictionaryLock unlock];

        [pool release];
    }
}

@end

#define COUNT    10000

static void doGets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
    {
        NSObject    *anObject;

        //    辞書の値を取得し、その後その値にメッセージを送る
        [aDictionaryLock lock];
        anObject = [aDictionary objectForKey: KEY];
        [[anObject retain] autorelease];
        [aDictionaryLock unlock];

        [anObject description];
    }
}

static void doSets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
    {
        NSObject    *anObject;

        anObject = [[NSObject alloc] init];

        [aDictionaryLock lock];
        [aDictionary setObject: anObject  forKey: KEY];
        [anObject autorelease];
        [aDictionaryLock unlock];

        [anObject description];
    }
}

int main ()
{
    SEL        threadSelector;

    [[NSAutoreleasePool alloc] init];

    threadSelector = @selector(churnContents);

    aDictionary = [NSMutableDictionary dictionary];
    aDictionaryLock = [[NSLock alloc] init];

     //    辞書の反復処理を開始。
    //    あるキーに対応する 1 つの値を繰り返し新しい値で置き換える
    [NSThread detachNewThreadSelector: threadSelector
                toTarget: aDictionary
                withObject: nil];

    doGets();
    doSets();

    return 0;
}

リスト 2 main2.m(「ダウンロード」セクション から入手可能)。上記のソースコードは、例 1 とは、2、3 行変わっているだけですが、もはやクラッシュは起こりません。

このコードでは、doGets() 関数と doSets() 関数は、自身を守るためにコードを追加または変更しています。doGets() 関数は、辞書から取得するオブジェクトを一時的に保持し、 その後 autorelease によりその保持効果を相殺します。 deSets 関数は、releaseautorelease に変更します。

辞書を反復処理するスレッドのコードが前と同じだということに注意してください。 このスレッドはオブジェクトを辞書から取り出すことも、また辞書に保存した値を使うこともありません。

データを安全に保つ役目を、このスレッドに担ってもらわないのはどうしてなのでしょうか。スレッドに、除去しようとしているオブジェクトの retain と autorelease を実行する役目を担ってもらわないのはどうしてでしょうか。なぜなら、各スレッドは各自、autorelease のプールを持っているからです。反復処理を実行するスレッドが、オブジェクトに retain、その後 autorelease を送った場合、この autorelease は、反復処理スレッドが自身の autorelease のプールを解放したときに有効になります。つまり、プールの解放は、ほかのスレッドがまだそのオブジェクトを使用しているときにも起こる可能性があります。

まとめると次のようになります。

  • retain と release は、一時的にオブジェクトが割り当てを解除されないようにする働きをしますが、release を送るのを忘れないようにしなければなりません。
  • retain と autorelease は、autorelease のプールが、デベロッパに代わって release を送ることを覚えておいてくれるので、より機能的かもしれません。
  • オブジェクトを保護しようとしているスレッドが、autorelease を行う必要があります。ほかのスレッドだと、不適切なタイミングで、オブジェクトが解放される可能性があります。

先頭に戻る



例 3:簡単に安全性を確保するためのカテゴリ

上記のコードでも目的は達成できますが、辞書を安全に利用するためには、辞書を使うコードごとに、同じ手順を踏まなければなりません。開発者が安全なコードをより簡単に記述できるようにするために、機能をカプセル化することもできます。その1つの方法は、 NSMutableDictionary にカテゴリを追加することです。そうすることにより、開発者に代わって大半の処理を行う複数のメソッドを追加できます。

次の3つのメソッドを追加するとします。

- (id) threadSafeObjectForKey: (id) aKey  usingLock: (NSLock *) aLock;
- (void) threadSafeRemoveObjectForKey: (id) aKey  usingLock: (NSLock *) aLock;
- (void) threadSafeSetObject: (id) anObject
            forKey: (id) aKey  usingLock: (NSLock *) aLock;

たとえば、コードが次のようになっているとします。

    [aDictionaryLock lock];
    anObject = [aDictionary objectForKey: KEY];
    [[anObject retain] autorelease];
    [aDictionaryLock unlock];

カテゴリを追加すれば、上記のコードは、次のようにステートメントを 1 つにできます。

anObject = [aDictionary threadSafeObjectForKey: KEY
        usingLock: aDictionaryLock];

次のソースコードには、改訂されたアプリケーションコードに加え、このカテゴリのインタフェース宣言と実装が含まれています(通常は、カテゴリのインタフェースはそれ自身の".h"ファイルに、カテゴリの実装はそれ自身の".m"ファイルに入れます。この例では、簡単にするために、すべてを1つのファイルに入れています)。

#import <Foundation/Foundation.h>

////////////////////////////////////////////////////////////////
////    NSMutableDictionary CATEGORY FOR THREAD-SAFETY
////////////////////////////////////////////////////////////////

@interface NSMutableDictionary (ThreadSafety)

- (id) threadSafeObjectForKey: (id) aKey
    usingLock: (NSLock *) aLock;

- (void) threadSafeRemoveObjectForKey: (id) aKey
    usingLock: (NSLock *) aLock;

- (void) threadSafeSetObject: (id) anObject
    forKey: (id) aKey
    usingLock: (NSLock *) aLock;

@end


@implementation NSMutableDictionary (ThreadSafety)

- (id) threadSafeObjectForKey: (id) aKey
    usingLock: (NSLock *) aLock;
{
    id    result;

    [aLock lock];
    result = [self objectForKey: aKey];
    [[result retain] autorelease];
    [aLock unlock];

    return result;
}

- (void) threadSafeRemoveObjectForKey: (id) aKey
    usingLock: (NSLock *) aLock;
{
    [aLock lock];
    [self removeObjectForKey: aKey];
    [aLock unlock];
}

- (void) threadSafeSetObject: (id) anObject
    forKey: (id) aKey
    usingLock: (NSLock *) aLock;
{
    [aLock lock];
    [[anObject retain] autorelease];
    [self setObject: anObject  forKey: aKey];
    [aLock unlock];
}

@end


////////////////////////////////////////////////////////////////
////    TEST PROGRAM
////////////////////////////////////////////////////////////////

static NSMutableDictionary    *aDictionary = nil;
static NSLock                *aDictionaryLock = nil;

@implementation NSMutableDictionary (Churning)

#define KEY        @"key"

- (void) churnContents;
{
    unsigned long    i;

    for (i = 0; ; i++)
    {
        NSAutoreleasePool    *pool;

        pool = [[NSAutoreleasePool alloc] init];
        [self threadSafeSetObject: [NSString stringWithFormat: @"%d", i]
            forKey: KEY  usingLock: aDictionaryLock];
        [pool release];
    }
}

@end

#define COUNT    10000

static void doGets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
        //    辞書の値を取得し、その後その値にメッセージを送信する
        [[aDictionary threadSafeObjectForKey: KEY
        usingLock: aDictionaryLock] description];
}

static void doSets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
    {
        NSObject    *anObject;

        anObject = [[NSObject alloc] init];
        [aDictionary threadSafeSetObject: anObject
            forKey: KEY
            usingLock: aDictionaryLock];
        [anObject release];
        [anObject description];
    }
}

int main ()
{
    SEL        threadSelector;

    [[NSAutoreleasePool alloc] init];

    threadSelector = @selector(churnContents);

    aDictionary = [NSMutableDictionary dictionary];
    aDictionaryLock = [[NSLock alloc] init];

    //    辞書の反復処理を開始。
    //    あるキーに対応する 1 つの値を繰り返し新しい値で置き換える

    [NSThread detachNewThreadSelector: threadSelector
                toTarget: aDictionary
                withObject: nil];

    doGets();
    doSets();

    return 0;
}

リスト 3 main3.m (「ダウンロード」セクション から入手可能)

これらのメソッドでは、辞書を制御するロックを指定するように要求されます。通常は、辞書ごとに 1 つの NSLock インスタンスを使うことを選択します。

ロック指定のもう 1 つの方法は、カテゴリに、自身でロックを提供する threadSafeObjectForKey: などの、より簡単なメソッドを提供させることです。この方法では、1 つのロックをすべての辞書に対して使うことになるか、各辞書が個別のロックにマッピングされる 1 つのデータ構造体を持つことになるという点で問題があります。データ構造体には、自身のロックが必要なため、どちらにしても、1 つのロックが、すべてのスレッドのボトルネックになる可能性があります。

各辞書にロックを簡単に関連付けるために、辞書クラスをサブクラス化することができます。次の例で、その方法を詳しく説明します。

先頭に戻る



例 4 NSMutableDictionary のサブクラス

カテゴリに threadSafeObjectForKey のようなメソッドを追加し、アプリケーションを作成するすべてのデベロッパにこのメソッドを使うよう要求する方法の代わりに、 NSMutableDictionary のサブクラスを作成し、 objectForKey: などのメソッドをオーバーライドして、スレッドセーフな実装で置き換えるという方法もあります。

NSMutableDictionary は、 NSDictionary クラスクラスタの一部です。クラスクラスタの内部でサブクラス化することは少々複雑なので、十分な理由がない場合は行うべきではありません。クラスクラスタおよびそれらをサブクラス化する方法をこれから学ぶ場合は、クラスクラスタの入門手引きを参照してください。

次の実装では、そのクラスクラスタの文献の中で説明されている「複合オブジェクト」技法を使っています。コードでは、可変辞書の実体と 1 つのロックを含むオブジェクトを定義します。また、 NSMutableDictionary の各プリミティブメソッドも実装しています。

#import <Foundation/Foundation.h>

////////////////////////////////////////////////////////////////
////    NSMutableDictionary SUBCLASS
////////////////////////////////////////////////////////////////

@interface ThreadSafeMutableDictionary : NSMutableDictionary
{
    NSMutableDictionary    *realDictionary;
    NSLock                *lock;
}

@end

@implementation ThreadSafeMutableDictionary : NSMutableDictionary

//  NSDictionaryにあるプリミティブメソッド

- (unsigned) count;
{
    //  このためのロックは必要ないと思われる
    return [realDictionary count];
}

- (NSEnumerator *) keyEnumerator;
{
    NSEnumerator    *result;

    //    この操作のためにロックする必要があるかどうかはっきりしない
    //    しかし、慎重になろう
    [lock lock];
    result = [realDictionary keyEnumerator];
    [lock unlock];

    return result;
}

- (id) objectForKey: (id) aKey;
{
    id    result;

    [lock lock];
    result = [realDictionary objectForKey: aKey];

    //  ロックを解除する前に、autorelease プールが解放されるまで、
    //  このオブジェクトの割り当てを解除されないようにする
  [[result retain] autorelease];
    [lock unlock];
 
    return result;
}


//  NSMutableDictionary のプリミティブメソッド
- (void) removeObjectForKey: (id) aKey;
{
    //    このメソッド自身が問題に遭遇することはないかもしれないが、
    //  ほかのスレッドを損なうことがないようにロックを尊重する
    [lock lock];
    [realDictionary removeObjectForKey: aKey];
    [lock unlock];
}

- (void) setObject: (id) anObject forKey: (id) aKey;
{
    //    オブジェクトを辞書に入れるとほかのスレッドによって
    //  解放される危険があるので、保護する
    [[anObject retain] autorelease];

    //    ロックを尊重する。なぜなら、オブジェクトを設定すると
    //  前のオブジェクトが解放される可能性があるから
    [lock lock];
    [realDictionary setObject: anObject  forKey: aKey];
    [lock unlock];
}


//    これはプリミティブではないが、最適化しよう
- (id) initWithCapacity: (unsigned) numItems;
{
    self = [self init];
    if (self != nil)
        realDictionary = [[NSMutableDictionary alloc] initWithCapacity: numItems];

    return self;
}

//    NSObjectに対するオーバーライド
- (id) init;
{
    self = [super init];
    if (self != nil)
        lock = [[NSLock alloc] init];

    return self;
}

- (void) dealloc;
{
    [realDictionary release];
    [lock release];

    [super dealloc];
}

@end


////////////////////////////////////////////////////////////////
////    TEST PROGRAM
////////////////////////////////////////////////////////////////

static NSMutableDictionary    *aDictionary = nil;

@implementation NSMutableDictionary (Churning)

#define KEY        @"key"

- (void) churnContents;
{
    unsigned long    i;

    for (i = 0; ; i++)
    {
        NSAutoreleasePool    *pool;

        pool = [[NSAutoreleasePool alloc] init];
        [self setObject: [NSString stringWithFormat: @"%d", i]  forKey: KEY];
        [pool release];
    }
}

@end

#define COUNT    10000

static void doGets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
        //    辞書の値を取得し、その後値にメッセージで送信する
        [[aDictionary objectForKey: KEY] description];
}

static void doSets (void)
{
    long    i;

    for (i = 0; i < COUNT; i++)
    {
        NSObject    *anObject;

        anObject = [[NSObject alloc] init];
        [aDictionary setObject: anObject  forKey: KEY];
        [anObject release];
        [anObject description];
    }
}

int main ()
{
    SEL        threadSelector;

    [[NSAutoreleasePool alloc] init];

    threadSelector = @selector(churnContents);

    aDictionary = [ThreadSafeMutableDictionary dictionary];

    //    辞書の反復処理を開始。
    //    あるキーに対応する 1 つの値を繰り返し新しい値で置き換える
    [NSThread detachNewThreadSelector: threadSelector
                toTarget: aDictionary
                withObject: nil];

    doGets();
    doSets();

    return 0;
}

リスト 4 main4.m (「ダウンロード」セクション から入手可能)

このサブクラスには、いくつかの問題があります。

  • ロックに要する時間がパフォーマンスに影響する場合があります。
  • さらに、どのサブクラスも、一般的には高度に最適化されているアップルの「確定」実装ほどは高速ではありません。
  • 各オブジェクトは、2 つの基本オブジェクトを使って実装され、より多くのメモリを使います。
  • 辞書は、必ずサブクラスからインスタンス化する必要があります。 この最終バージョンでは、辞書は、次のコードで作成されます。
    aDictionary = [ThreadSafeMutableDictionary dictionary];

まとめると次のようになります。

  • カテゴリは、既存クラスを基に新機能をカプセル化する簡単な方法を提供します。
  • サブクラス化は、メソッドを追加するのではなく、メソッドをオーバーライドすることにより機能を追加するもう1つの方法を提供します。
  • クラスクラスタ内でのサブクラス化には特別の注意が必要です。

先頭に戻る



要約

NSMutableDictionary インスタンスに関連してスレッドの安全上の問題を解決する 4 つの方法を見てきました(同様の方法を、NSMutableArrayNSMutableSet にも適用できます)。 各方法にはそれぞれ利点と問題点があります。問題点を常に忘れないようにし、マルチスレッドの環境で操作するときのみこれらのクラスを使います。復習として、利点と問題点を次にまとめます。

例 1 ロックを使用して可変辞書へのアクセスを制御
利点:単純明快なコード
問題点: 当たり前のにようにクラッシュする

例 2 retainautorelease を使用してオブジェクトを保護
利点:クラッシュしない
問題点:クライアントコードは安全のために一定のルールに従う必要あり
autorelease を使うと余分の時間と一時的に余分なスペースが必要

例 3 カテゴリでカプセル化した安全なメソッドを追加
利点: クラッシュしない。クライアントコードの簡素化
問題点:クライアントコードは、新しいメソッドを使用し、ロックオブジェクトを提供する必要あり
autorelease を使うと余分の時間と一時的に余分なスペースが必要

例 4 サブクラスを使ってNSMutableDictionary を安全にする
利点:クラッシュしない。クライアントコード変更の必要なし
問題点: 新しいクラスから辞書をインスタンス化する必要あり
パフォーマンスはアップルの実装に比べ劣る
autorelease を使うと余分の時間と一時的に余分なスペースが必要

先頭に戻る



参考文献

Overview of Programming Topic: Multithreading

Thread Safety

Using Foundation from Multiple Threads

Overview of Core Foundation

先頭に戻る



ダウンロード

Acrobat gif

このテクニカルノートのPDF版 (80K)

ダウンロード

Redbook gif

サンプルコード(8K)

ダウンロード


先頭に戻る